﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Simple_search.Data;
using System.IO;
using System.Text.RegularExpressions;
using System.CodeDom.Compiler;
using Prism.Events;
using System.Diagnostics;

namespace Simple_search.Service
{
    public class SearchManager
    {
        public SearchManager(EventAggregator eventAggregator, CacheManager cacheManager)
        {
            _eventAggregator = eventAggregator;
            _cacheManager = cacheManager;
        }
        private EventAggregator _eventAggregator;
        private CacheManager _cacheManager;

        private string DirectoryPath;
        private List<IBook> Books = new List<IBook>();
        private int BlockSize;
        private int Threshold;
        private bool IsFuzzy;
        private string[] ValidExtensions = { ".txt", ".TXT", ".epub", ".EPUB", ".Epub"};

        private Dictionary<IBook, List<BlockResult>>? searchResult = null;

        public void SetProfile(string directoryPath, int blockSize, int threshold, bool isFuzzy)
        {
            DirectoryPath = directoryPath;
            BlockSize = blockSize;
            Threshold = threshold;
            IsFuzzy = isFuzzy;
            Books.Clear();
            TraversalDirectoryAndRegisterBook(DirectoryPath);
        }

        private void RegisterBook(string bookFilePath)
        {
            if (ValidExtensions.Contains<string>(Path.GetExtension(bookFilePath)))
            {
                string bookname = Path.GetFileNameWithoutExtension(bookFilePath);
                _eventAggregator.GetEvent<EventStatusChange>().Publish(Tuple.Create(BackgroundStatus.Initialize, bookname));
                if (Path.GetExtension(bookFilePath).ToLower() == ".epub")
                {
                    Books.Add(new EpubBook(bookFilePath, BlockSize));
                }
                else
                {
                    string? encodeName = _cacheManager.GetBookEncode(bookFilePath);
                    if (encodeName != null)
                    {
                        Books.Add(new TextBook(bookFilePath, BlockSize, encodeName));
                    }
                    else
                    {
                        encodeName = TextBook.GetEncode(bookFilePath);
                        IBook book = new TextBook(bookFilePath, BlockSize, encodeName);
                        _cacheManager.SaveBookInfo(bookFilePath, encodeName);
                        Books.Add(book);
                    }
                }
            }
        }

        private void TraversalDirectoryAndRegisterBook(string directoryPath)
        {
            foreach (string entry in Directory.EnumerateFileSystemEntries(directoryPath))
            {
                if (File.Exists(entry))
                {
                    RegisterBook(entry);
                }
                else if (Directory.Exists(entry))
                {
                    TraversalDirectoryAndRegisterBook(entry);
                }
            }
        }

        public Dictionary<IBook, List<BlockResult>> Search(List<SearchUnit> searchUnits)
        {
            foreach (IBook book in Books)
            {
                _eventAggregator.GetEvent<EventStatusChange>().Publish(Tuple.Create(BackgroundStatus.Searching, book.Name));
                using (book.Open())
                {
                    foreach(SearchUnit searchUnit in searchUnits)
                    {
                        List<SearchResult> unitResults = new();
                        foreach (Regex term in IsFuzzy ? searchUnit.FuzzyTerms : searchUnit.Terms)
                        {
                            unitResults.AddRange(book.Search(term));
                        }
                        List<SearchResult> mergedResults = SearchResult.Merge(unitResults);
                        if (mergedResults.Count > 0)
                        {
                            searchUnit.AddResult(book, mergedResults);
                        }
                    }
                }
            }

            searchResult = MergeSearchUnitsByBlock(searchUnits);
            return searchResult;
        }

        public Dictionary<IBook, List<BlockResult>> GetSearchResult()
        {
            if (searchResult != null)
            {
                return searchResult;
            }
            else
            {
                throw new Exception("Empty result!");
            }
        }

        public void Clear()
        {
            searchResult = null;
        }

        private Dictionary<IBook, List<BlockResult>> MergeSearchUnitsByBlock(List<SearchUnit> searchUnits)
        {
            _eventAggregator.GetEvent<EventStatusChange>().Publish(Tuple.Create(BackgroundStatus.Merge, ""));
            Dictionary<IBook, List<BlockResult>> mergedResult = new();
            foreach (IBook book in Books)
            {
                List<BlockCombination> bloks = new();
                foreach (SearchUnit searchUnit in searchUnits)
                {
                    if (searchUnit.Result.ContainsKey(book))
                    {
                        foreach(SearchResult result in searchUnit.Result[book])
                        {
                            bloks.Add(new BlockCombination(searchUnit, result));
                        }
                    }
                }
                if (bloks.Count > 0)
                {
                    List<BlockResult> blockResults = new();
                    IOrderedEnumerable<BlockCombination> orderedBlocks = bloks.OrderBy(block => block.StartBlockId);

                    int endBlockId = 0;
                    BlockCombination laskBlock = BlockCombination.Empty;
                    List<BlockCombination> blockHeap = new();

                    foreach (BlockCombination block in orderedBlocks)
                    {
                        if (laskBlock.IsEmpty)
                        {
                            endBlockId = block.EndBlockId;
                            laskBlock = block;
                        }
                        else if (block.StartBlockId <= endBlockId)
                        {
                            blockHeap.Add(laskBlock);
                            laskBlock = block;
                            endBlockId = endBlockId < block.EndBlockId ? block.EndBlockId : endBlockId;
                        } else
                        {
                            if (blockHeap.Count > 0)
                            {
                                blockHeap.Add(laskBlock);
                                blockResults.Add(new(book, blockHeap, endBlockId));
                                blockHeap.Clear();
                            } else
                            {
                                blockResults.Add(new(book, laskBlock));
                            }
                            laskBlock = block;
                            endBlockId = block.EndBlockId;
                        }
                    }
                    if (blockHeap.Count > 0)
                    {
                        blockHeap.Add(laskBlock);
                        blockResults.Add(new(book, blockHeap, endBlockId));
                    }
                    else
                    {
                        blockResults.Add(new(book, laskBlock));
                    }

                    List<BlockResult> filteredBlockResults = blockResults.Where(x => x.ResultCombination.Count >= Threshold).ToList<BlockResult>();

                    mergedResult.Add(book, filteredBlockResults);
                }
            }
            return mergedResult;
        }
    }

}
